'''
    Server module - manage each IRC server connection
'''

import pyPopeye_channel as channel
import message
import modes
import regexes

import asynchat
import socket
import ssl
import weakref
import os
import time
import thread

class server(asynchat.async_chat):
    channels = []
    mode = ''
    
    def __init__(self, server_host, port, conn_info, popeye):
        '''
            string server_host- the IRC host that we're connecting to
            int    port       - the port on which we'll connect
            dict   userinfo   - dictionary containing nick, username, hostname,
                                 realname, servername entries.
        '''
        
        self.conn_info = conn_info
        self.popeye = popeye
        
        self.server_host, self.port = server_host, port
        
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((server_host, port))
        except:
            print "Couldn't connect."
            raise
            
        if conn_info['ssl']:
            sock = ssl.wrap_socket(sock)
            print "Connecting with SSL: %s" % str(sock.cipher())
            
        asynchat.async_chat.__init__(self, sock)
        
        sock.settimeout(30.0)
        
        print 'Connecting to ' + server_host
        
        data = '433'
        while data.find('433') >= 0:
            print >> self, 'NICK %(nick)s' % conn_info
            data = sock.recv(255)
            conn_info['nick'] += '_'
        conn_info['nick'] = conn_info['nick'][:-1]
        
        if regexes.ping.match(data):
            print >> self, 'PONG :' + data.split(':')[1]

        
        print >> self, 'USER %(username)s %(hostname)s %(servername)s :%(realname)s' % conn_info
                
        data = sock.recv(255)
        print data
        while data.find('001') < 0:
            #MUST wait for code 001 (welcome)
            data = sock.recv(255)
            print data
        print 'Now welcomed on ' + server_host
        
        #mark self as bot
        print >> self, 'MODE %(nick)s +B' % conn_info
        
        if 'nickpass' in conn_info:
            print >> self, 'NS IDENTIFY %(nickpass)s' % conn_info
        
        for i in conn_info.get('channels',[]).split(','):
            #default channel prefix is #, which can't be specified in the conf file
            i = i.strip()
            if len(i) and i[0] != '&':
                i = '#' + i
            self.join(i)

        thread.start_new_thread(check_and_reload, (weakref.ref(self),))

        self.data = []
        self.set_terminator('\n')

    def join(self, chan):
        self.channels.append(channel.pyPopeye_channel(weakref.ref(self), chan))
    
    def write(self, data):
        if data != '\n':
            self.push(data + '\r\n')
    
    def collect_incoming_data(self, data):
        self.data.append(data)
    
    def get_chan_by_name(self, name):
        for i in self.channels:
            if i.chan_name.lower() == name:
                return i
    
    def _process_data(self, data):
        '''
            Offloads regex matching from found_terminator
        '''
        
        command = {}
        
        #attempt to guess the channel regardless of the command
        guess_chan = regexes.guess_channel.search(data)
        if guess_chan:
            command['channel'] = guess_chan.group(1)
        
        for i in ('join', 'part', 'quit', 'nick',
                  'topic', 'privmsg', 'notice',
                  'mode', 'kick', 'ping'):
            m = getattr(regexes, i).match(data)
            if m:
                command[i] = m.groupdict()
                
                #A more specific regex is more likely to know
                #where the channel name is supposed to go
                if 'channel' in command[i]:
                    command['channel'] = command[i]['channel']
                break
        
        #Private messages get their own special handling
        if 'privmsg' in command:
            command['message'] = message.message(data, command.get('privmsg', None), command.get('notice', None))
        
        return command
        
    def on_peer_quit(self, nick):
        '''
            Notify all channels of quits
        '''
        
        for i in self.channels:
            if nick in i.users:
                i.on_quit(nick)
    
    def found_terminator(self):
        data = ''.join(self.data).rstrip('\r')
        self.data = []
        
        c = self._process_data(data)
        
        
        channel, channel_obj = None, None
        if 'channel' in c:
            channel = c['channel']
            channel_obj = self.get_chan_by_name(channel)

        if 'ping' in c: 
            print data
            print >> self, 'PONG :%s' % c['ping']['servername']
            print ' Responding to ping from %(servername)s' % c['ping']
    
        
        if 'channel' in c:
            if channel_obj and channel_obj.raw:
                if channel_obj.on_raw(data):
                    return
        
        if 'nick' in c:
            updated = []
            nick = c['nick']['nick']
            newnick = c['nick']['newnick']
            for i in self.channels:
                if i.on_nick(nick, newnick):
                    updated.append(i.chan_name)
            print '%s changed nick to %s.  Updated %s' % (nick, newnick, ', '.join(updated))
            return
        
        if 'mode' in c and not channel_obj:
            print 'Own mode set ' + c['mode']['mode']
            self.mode = modes.arithmetic(self.mode, c['mode']['mode'])
            print 'Own mode is now +' + self.mode
            return
        
        if channel_obj:
            #First item is the key that needs to be passed to the function
            #second item is the function
            actions = {
                       'part': (('nick',), channel_obj.on_part),
                       'join': (('nick',), channel_obj.on_join),
                       'topic': (('topic',), channel_obj.on_topic),
                       'kick': (('nick',), channel_obj.on_kick),
                       'mode': (('hoststr','mode'), channel_obj.on_mode),
                       'quit': (('nick',), self.on_peer_quit)
                       }
            
            for i in actions:
                if i in c:
                    args = [c[i][j] for j in actions[i][0]]
                    fn = actions[i][1]
                    
                    fn(*args)
                    return
                    
            if 'message' in c and c['message'].ok:
                if c['message'].private:
                    print '(PRIV) <%(nick)s> %(message)s' % c['message'].__dict__
                if channel_obj is not None:
                    channel_obj.on_msg(c['message'])
                    return
        
        #If the channel object is null, this is a server message
        #most of which we don't bother to deal with
        
        #Some messages we just don't care to see
        for i in ['003','004','005','333']:
            if i in data:
                return
        
        print data
        
    def quit(self,  msg = ''):
        print >>self,  'QUIT :%s' % msg
        self.popeye().remove(self)

    def dynamic_reload(self):
        print 'Dynamic reload in progress on all channels.'
        chan_names = []
        for i in self.channels:
            print >>i, 'Executing dynamic reload.  Please wait.'
            chan_names.append(i.chan_name)
            i.part('', True)
            del i
        
        reload(channel)
        
        for i in chan_names:
            c = channel.pyPopeye_channel(weakref.ref(self), i, True)
            self.channels.append(c)
            print >>c, 'Dynamic reload successful.'

def check_and_reload(server_ref):
    atime =  os.stat(channel.__file__)[7]

    while 1:
        new_atime = os.stat(channel.__file__)[7]
        if new_atime != atime:
            try:
                print "%s updated, reloading" % channel.__file__
                server_ref().dynamic_reload()
                atime = new_atime
            except AttributeError:
                # program exit
                return
        time.sleep(1)
